/**
 * 
 * @Project le cOeur - Data Virtualization
 * @Date 08.12.2012
 * @Author Aebi Pascal, Kocher Marc
 * @School BFH-TI Biel, Bachelor Thesis
 *
 */

package main.framework;

import java.util.logging.Logger;

import main.framework.cache.CacheEntry;
import main.framework.cache.CacheManager;
import main.framework.exception.CachingException;
import main.framework.exception.NoRepresentationCreatorFoundException;
import main.framework.exception.RepresentationCreationException;
import main.framework.exception.ResourceMethodExecutionException;
import main.framework.exception.SecurityAccessException;
import main.framework.http.MessageBody;
import main.framework.http.MessageHeader;
import main.framework.http.Method;
import main.framework.http.ReasonPhrase;
import main.framework.http.Request;
import main.framework.http.Response;
import main.framework.http.StatusCode;
import main.framework.representation.RepresentationCreator;
import main.framework.representation.RepresentationCreatorManager;
import main.framework.resource.DataGetterResourceMethod;
import main.framework.resource.Resource;
import main.framework.resource.ResourceData;
import main.framework.resource.ResourceManager;
import main.framework.security.ExtendedSecurityManager;

/**
 * This class defines how HTTP the requests are handled.
 */
public class RequestHandler {
	
	private ResponseFactory httpResponseFactory;
	private ResourceManager resourceManager;
	private RepresentationCreatorManager representationCreatorManager;
	private CacheManager cacheManager;
	private ExtendedSecurityManager securityManager;
	private Logger logger;
	
	/**
	 * Creates a HTTP request handler.
	 * 
	 * @param resourceManager The resource manager.
	 * @param representationCreatorManager The representation creation manager.
	 * @param cacheManager The cache manager.
	 * @param securityManager The security manager.
	 */
	public RequestHandler(ResourceManager resourceManager, RepresentationCreatorManager representationCreatorManager, CacheManager cacheManager, ExtendedSecurityManager securityManager) {
		this.resourceManager = resourceManager;
		this.representationCreatorManager = representationCreatorManager;
		this.cacheManager = cacheManager;
		this.securityManager = securityManager;
		this.httpResponseFactory = new ResponseFactory();
		this.logger = Logger.getLogger("SysLogger");
	}
	
	/**
	 * Handles the given HTTP request and returns a corresponding HTTP response.
	 * 
	 * @param request The HTTP request.
	 * @return The corresponding HTTP response.
	 */
	public synchronized Response handleRequest(Request request) {
		// Set default representation creator
		RepresentationCreator representationCreator = this.representationCreatorManager.getDefaultRepresentationCreator();
		try {
			// Check if the remote host is allowed to access the server
			MessageHeader hostHeader = request.getMessageHeader("Host");
			String host = null;
			if(hostHeader != null) {
				host = hostHeader.getFieldValue();
			}
			this.securityManager.checkServerAccess(host);
			
			// Check if the desired HTTP method is allowed to execute on the resource
			// TODO: To implement
			
			// Get acceptable representation creator from accept request message header
			MessageHeader acceptHeader = request.getMessageHeader("Accept");
			if(acceptHeader != null){
				representationCreator = this.representationCreatorManager.getRepresentationCreator(acceptHeader);
			}

			if(request.getMethod() == Method.GET){
				return this.doGET(request, representationCreator);
			}
			else if(request.getMethod() == Method.PUT){
				return this.doPUT(request, representationCreator);
			}
			else if(request.getMethod() == Method.POST){
				return this.doPOST(request, representationCreator);
			}
			else if(request.getMethod() == Method.DELETE){
				return this.doDELETE(request, representationCreator);
			}
			else{
				return this.methodNotSupported(request, representationCreator);
			}
		} catch (NoRepresentationCreatorFoundException e) {
			return this.notAcceptable(request, representationCreator);
		} catch (SecurityAccessException e) {
			return this.forbidden(request, representationCreator);
		}
	}
	
	/**
	 * Executes the defined actions of the HTTP GET method.
	 * It creates a HTTP response containing a representation of the resource and returns it.
	 * 
	 * @param request The HTTP request.
	 * @param representationCreator The representation creator of the desired representation.
	 * @return The corresponding HTTP response.
	 */
	private Response doGET(Request request, RepresentationCreator representationCreator) {
		Resource resource = this.resourceManager.getResource(request.getRequestedURI());
		if(resource == null) return this.uriNotSupported(request, representationCreator);
		DataGetterResourceMethod resourceMethod = (DataGetterResourceMethod) resource.getResourceMethod(Method.GET);
		if(resourceMethod == null) return this.methodNotSupported(request, representationCreator);
		try {
			// Get cache entry
			CacheEntry cacheEntry = this.cacheManager.getCacheEntry(request, representationCreator.getContentType());
			if (cacheEntry != null) {
				return cacheEntry.getResponse();
			}
			resourceMethod.execute(resource);
			ResourceData resourceData = resourceMethod.getData();
			MessageBody messageBody = representationCreator.createRepresentation(resourceData);
			Response response = this.httpResponseFactory.createResponse(request.getVersion(), StatusCode.OK, ReasonPhrase.STATUS_200, messageBody);
			// Set cache entry
			this.cacheManager.setCacheEntry(request, response);
			return response;
		} catch (ResourceMethodExecutionException e) {
			this.logger.severe(e.getMessage());
			return this.internalServerError(request, representationCreator);
		} catch (RepresentationCreationException e) {
			// TODO: Think about the principle of the choose of alternative representation creators
			this.logger.severe(e.getMessage());
			return this.createTextRespresentation(request, resourceMethod.getData());
		} catch (CachingException e) {
			return this.badRequest(request, representationCreator);
		}
	}

	/**
	 * Executes the defined actions of the HTTP PUT method.
	 * 
	 * @param request The request.
	 * @param representationCreator The representation creator of the desired representation.
	 * @return The corresponding HTTP response.
	 */
	private Response doPUT(Request request, RepresentationCreator representationCreator) {
		return this.notImplemented(request, representationCreator);
	}
	
	/**
	 * Executes the defined actions of the HTTP POST method.
	 * 
	 * @param request The request.
	 * @param representationCreator The representation creator of the desired representation.
	 * @return The corresponding HTTP response.
	 */
	private Response doPOST(Request request, RepresentationCreator representationCreator) {
		return this.notImplemented(request, representationCreator);
	}
	
	/**
	 * Executes the defined actions of the HTTP DELETE method.
	 * 
	 * @param request The request.
	 * @param representationCreator The representation creator of the desired representation.
	 * @return The corresponding HTTP response.
	 */
	private Response doDELETE(Request request, RepresentationCreator representationCreator) {
		return this.notImplemented(request, representationCreator);
	}
	
	/**
	 * Creates and then returns HTTP response containing a text representation of the resource and returns it.
	 * 
	 * @param request The request.
	 * @param data The data which should be converted to a representation.
	 * @return The response.
	 */
	private Response createTextRespresentation(Request request, ResourceData resourceData) {
		MessageBody messageBody = this.representationCreatorManager.getFallbackRepresentationCreator().createRepresentation(resourceData);
		Response response = this.httpResponseFactory.createResponse(request.getVersion(), StatusCode.OK, ReasonPhrase.STATUS_200, messageBody);
		return response;
	}
	
	/**
	 * Creates and then returns a HTTP response if the the HTTP request has a malformed syntax (error 400).
	 * 
	 * @param request The request.
	 * @return The corresponding HTTP response.
	 */
	private Response badRequest(Request request, RepresentationCreator representationCreator) {
		MessageBody messageBody;
		try {
			messageBody = representationCreator.createRepresentation(StatusCode.BAD_REQUEST.getValue() + " " + ReasonPhrase.STATUS_400.getValue());
		} catch (RepresentationCreationException e) {
			messageBody = this.representationCreatorManager.getFallbackRepresentationCreator().createRepresentation(StatusCode.BAD_REQUEST.getValue() + " " + ReasonPhrase.STATUS_400.getValue());
		}
		return this.httpResponseFactory.createResponse(request.getVersion(), StatusCode.BAD_REQUEST, ReasonPhrase.STATUS_400, messageBody);
	}
	
	/**
	 * Creates and then returns a HTTP response if the remote host is not allowed to access the server (error 403).
	 * 
	 * @param request The request.
	 * @return The corresponding HTTP response.
	 */
	private Response forbidden(Request request, RepresentationCreator representationCreator) {
		MessageBody messageBody;
		try {
			messageBody = representationCreator.createRepresentation(StatusCode.FORBIDDEN.getValue() + " " + ReasonPhrase.STATUS_403.getValue());
		} catch (RepresentationCreationException e) {
			messageBody = this.representationCreatorManager.getFallbackRepresentationCreator().createRepresentation(StatusCode.FORBIDDEN.getValue() + " " + ReasonPhrase.STATUS_403.getValue());
		}
		return this.httpResponseFactory.createResponse(request.getVersion(), StatusCode.FORBIDDEN, ReasonPhrase.STATUS_403, messageBody);
	}
	
	
	/**
	 * Creates and then returns a HTTP response if the requested resource URI is not supported (error 404).
	 * 
	 * @param request The request.
	 * @return The corresponding HTTP response.
	 */
	private Response uriNotSupported(Request request, RepresentationCreator representationCreator) {
		MessageBody messageBody;
		try {
			messageBody = representationCreator.createRepresentation(StatusCode.NOT_FOUND.getValue() + " " + ReasonPhrase.STATUS_404.getValue() + " - URL: " + request.getRequestedURI().getPath() + " was not found on this server.");
		} catch (RepresentationCreationException e) {
			messageBody = this.representationCreatorManager.getFallbackRepresentationCreator().createRepresentation(StatusCode.NOT_FOUND.getValue() + " " + ReasonPhrase.STATUS_404.getValue() + " - URL: " + request.getRequestedURI().getPath() + " was not found on this server.");
		}
		return this.httpResponseFactory.createResponse(request.getVersion(), StatusCode.NOT_FOUND, ReasonPhrase.STATUS_404, messageBody);
	}
	
	/**
	 * Creates and then returns a HTTP response if the requested method is not supported by the resource (error 405).
	 * 
	 * @param request The request.
	 * @return The corresponding HTTP response.
	 */
	private Response methodNotSupported(Request request, RepresentationCreator representationCreator) {
		MessageBody messageBody;
		try {
			messageBody = representationCreator.createRepresentation(StatusCode.METHOD_NOT_ALLOWED.getValue() + " " + ReasonPhrase.STATUS_405.getValue() + " - Method: " + request.getMethod().toString() + " is not allowed on this server.");
		} catch (RepresentationCreationException e) {
			messageBody = this.representationCreatorManager.getFallbackRepresentationCreator().createRepresentation(StatusCode.METHOD_NOT_ALLOWED.getValue() + " " + ReasonPhrase.STATUS_405.getValue() + " - Method: " + request.getMethod().toString() + " is not allowed on this server.");
		}
		return this.httpResponseFactory.createResponse(request.getVersion(), StatusCode.METHOD_NOT_ALLOWED, ReasonPhrase.STATUS_405, messageBody);
	}
	
	/**
	 * Creates and then returns a HTTP response if there were no supported representations in the request (error 406).
	 * 
	 * @param request The request.
	 * @return The corresponding HTTP response.
	 */
	private Response notAcceptable(Request request, RepresentationCreator representationCreator) {
		MessageBody messageBody;
		try {
			messageBody = representationCreator.createRepresentation(StatusCode.NOT_ACCEPTABLE.getValue() + " " + ReasonPhrase.STATUS_406.getValue());
		} catch (RepresentationCreationException e) {
			messageBody = this.representationCreatorManager.getFallbackRepresentationCreator().createRepresentation(StatusCode.NOT_ACCEPTABLE.getValue() + " " + ReasonPhrase.STATUS_406.getValue());
		}
		return this.httpResponseFactory.createResponse(request.getVersion(), StatusCode.NOT_ACCEPTABLE, ReasonPhrase.STATUS_406, messageBody);
	}
	
	/**
	 * Creates and then returns a HTTP response if the server encountered an unexpected condition which prevented it from fulfilling the request (error 500).
	 * 
	 * @param request The request.
	 * @return The corresponding HTTP response.
	 */
	private Response internalServerError(Request request, RepresentationCreator representationCreator) {
		MessageBody messageBody;
		try {
			messageBody = representationCreator.createRepresentation(StatusCode.INTERNAL_SERVER_ERROR.getValue() + " " + ReasonPhrase.STATUS_500.getValue());
		} catch (RepresentationCreationException e) {
			messageBody = this.representationCreatorManager.getFallbackRepresentationCreator().createRepresentation(StatusCode.INTERNAL_SERVER_ERROR.getValue() + " " + ReasonPhrase.STATUS_500.getValue());
		}
		return this.httpResponseFactory.createResponse(request.getVersion(), StatusCode.INTERNAL_SERVER_ERROR, ReasonPhrase.STATUS_500, messageBody);
	}
	
	/**
	 * Creates and then returns a HTTP response if the server has not implemented a requested function (error 501).
	 * 
	 * @param request The request.
	 * @return The corresponding HTTP response.
	 */
	private Response notImplemented(Request request, RepresentationCreator representationCreator) {
		MessageBody messageBody;
		try {
			messageBody = representationCreator.createRepresentation(StatusCode.NOT_IMPLEMENTED.getValue() + " " + ReasonPhrase.STATUS_501.getValue());
		} catch (RepresentationCreationException e) {
			messageBody = this.representationCreatorManager.getFallbackRepresentationCreator().createRepresentation(StatusCode.NOT_IMPLEMENTED.getValue() + " " + ReasonPhrase.STATUS_501.getValue());
		}
		return this.httpResponseFactory.createResponse(request.getVersion(), StatusCode.NOT_IMPLEMENTED, ReasonPhrase.STATUS_501, messageBody);
	}
	
}
